#include <stdio.h>
#include <iostream>
#include "buf.h"
#include "bufMap.h"


// This file contains some rudimentary tests to help you test
// your buffer manager.
//
// Please note that when grading, we will use additional tests not included in this file!
//


#define CALL(c)    { Status s; \
    if ((s = c) != OK) { \
        cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
        Error::print(s); \
        exit(1); \
    } \
}

#define ASSERT(c)  { if (!(c)) { \
    cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
    cerr << "This condition should hold: " #c << endl; \
    exit(1); \
} \
}

#define FAIL(c)  { Status s; \
    if ((s = c) == OK) { \
        cerr << "At " << __FILE__ << ":" << __LINE__ << endl << "  "; \
        cerr << "This call should fail: " #c << endl; \
        exit(1); \
    } \
    else \
    { \
        cout << "Failing as expected while executing: " #c << endl; \
    } \
}

BufMgr*     bufMgr;

int main()
{
    DB          db;
    File*	file1;
    File*	file2;
    File* 	file3;
    File*   file4;
    int		i;
    const int   num = 50;
    int         j[num*2];    

    // create buffer manager 
    bufMgr = new BufMgr(num);

    // create some files

    (void)db.destroyFile("test.1");
    (void)db.destroyFile("test.2");
    (void)db.destroyFile("test.3");
    (void)db.destroyFile("test.4");

    CALL(db.createFile("test.1"));
    ASSERT(db.createFile("test.1") == FILEEXISTS);
    CALL(db.createFile("test.2"));
    ASSERT(db.createFile("test.2") == FILEEXISTS);
    CALL(db.createFile("test.3"));
    ASSERT(db.createFile("test.3") == FILEEXISTS);
    CALL(db.createFile("test.4"));
    ASSERT(db.createFile("test.4") == FILEEXISTS);

    CALL(db.openFile("test.1", file1));
    CALL(db.openFile("test.2", file2));
    CALL(db.openFile("test.3", file3));
    CALL(db.openFile("test.4", file4));


    // test buffer manager

    Page* page;
    Page* page2;
    Page* page3;
    Page cmp;
    int pageno, pageno2, pageno3;

    int num2 = 2*num;
    cout << "Allocating " << num2 << " pages in a file... " << endl;

    for (i = 0; i < num2; i++) {
        CALL(bufMgr->allocPage(file1, j[i], page));
        sprintf((char*)page, "test.1 Page %d %7.1f", j[i], (float)j[i]);
        CALL(bufMgr->unPinPage(file1, j[i], true, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();


    cout << "Reading pages back..." << endl;
    for (i = 0; i < num2; i++) {
        CALL(bufMgr->readPage(file1, j[i], page));
        sprintf((char*)&cmp, "test.1 Page %d %7.1f", j[i], (float)j[i]);
        ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);
        CALL(bufMgr->unPinPage(file1, j[i], false, false)); 
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();



    cout << "Writing and reading back multiple files..." << endl;
    for (i = 0; i < num/3; i++) {
        CALL(bufMgr->allocPage(file2, pageno2, page2));
        sprintf((char*)page2, "test.2 Page %d %7.1f", pageno2, (float)pageno2);
        CALL(bufMgr->allocPage(file3, pageno3, page3));
        sprintf((char*)page3, "test.3 Page %d %7.1f", pageno3, (float)pageno3);

        if ( i == 0)
        {
            cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
        }

        pageno = j[random() % num];
        CALL(bufMgr->readPage(file1, pageno, page));
        sprintf((char*)&cmp, "test.1 Page %d %7.1f", pageno, (float)pageno);
        ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);
        cout << (char*)page << endl;

        CALL(bufMgr->readPage(file2, pageno2, page2));
        sprintf((char*)&cmp, "test.2 Page %d %7.1f", pageno2, (float)pageno2);
        ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
        CALL(bufMgr->readPage(file3, pageno3, page3));
        sprintf((char*)&cmp, "test.3 Page %d %7.1f", pageno3, (float)pageno3);
        ASSERT(memcmp(page3, &cmp, strlen((char*)&cmp)) == 0);

        CALL(bufMgr->unPinPage(file1, pageno, true, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    for (i = 0; i < num/3; i++) {
        CALL(bufMgr->unPinPage(file2, i+1, true, false));
        CALL(bufMgr->unPinPage(file2, i+1, true, false));
        CALL(bufMgr->unPinPage(file3, i+1, true, false));
        CALL(bufMgr->unPinPage(file3, i+1, true, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

#ifdef DEBUGBUF
    bufMgr->printSelf();
#endif 

    cout << "\nReading \"test.1\"...\n";
    for (i = 1; i < num/3; i++) {
        CALL(bufMgr->readPage(file1, i, page2));
        sprintf((char*)&cmp, "test.1 Page %d %7.1f", i, (float)i);
        ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
        CALL(bufMgr->unPinPage(file1, i, false, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "\nReading \"test.2\"...\n";
    for (i = 1; i < num/3; i++) {
        CALL(bufMgr->readPage(file2, i, page2));
        sprintf((char*)&cmp, "test.2 Page %d %7.1f", i, (float)i);
        ASSERT(memcmp(page2, &cmp, strlen((char*)&cmp)) == 0);
        cout << (char*)page2 << endl;
        CALL(bufMgr->unPinPage(file2, i, false, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    cout << "\nReading \"test.3\"...\n";
    for (i = 1; i < num/3; i++) {
        CALL(bufMgr->readPage(file3, i, page3));
        sprintf((char*)&cmp, "test.3 Page %d %7.1f", i, (float)i);
        ASSERT(memcmp(page3, &cmp, strlen((char*)&cmp)) == 0);
        cout << (char*)page3 << endl;
        CALL(bufMgr->unPinPage(file3, i, false, false));
    }

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();



    cout << "\nTesting error conditions...\n\n";

    Status status;
    FAIL(status = bufMgr->readPage(file4, 1, page));
    Error::print(status);
    CALL(bufMgr->allocPage(file4, i, page));
    CALL(bufMgr->unPinPage(file4, i, true, false));
    FAIL(status = bufMgr->unPinPage(file4, i, false, false));
    Error::print(status);

    for (i = 0; i < num; i++) {
        CALL(bufMgr->allocPage(file4, j[i], page));
        sprintf((char*)page, "test.4 Page %d %7.1f", j[i], (float)j[i]);
    }
    int tmp;
    FAIL(status = bufMgr->allocPage(file4, tmp, page));
    Error::print(status);

#ifdef DEBUGBUF
    bufMgr->printSelf();
#endif 

    for (i = 0; i < num; i++)
        CALL(bufMgr->unPinPage(file4, i+2, true, false));

    cout << "\nReading \"test.1\"...\n";
    for (i = 1; i < num; i++) {
        CALL(bufMgr->readPage(file1, i, page));
        sprintf((char*)&cmp, "test.1 Page %d %7.1f", i, (float)i);
        ASSERT(memcmp(page, &cmp, strlen((char*)&cmp)) == 0);

        if ( i < num/2 )
        {
            sprintf((char*)page, "test.1 Page %d %7.1f", i, (float)i);
            CALL(bufMgr->unPinPage(file1, i, true, false)); // make the page dirty
            CALL(bufMgr->readPage(file1, i, page));  // make the page pinned 
        }
        cout << (char*)page << endl;
    }

    FAIL(status = bufMgr->flushFile(file1));
    Error::print(status);

    for (i = 1; i < num; i++) 
        CALL(bufMgr->unPinPage(file1, i, true, false));

    CALL(bufMgr->flushFile(file1));

    CALL(db.closeFile(file1));
    CALL(db.closeFile(file2));
    CALL(db.closeFile(file3));
    CALL(db.closeFile(file4));

    CALL(db.destroyFile("test.1"));
    CALL(db.destroyFile("test.2"));
    CALL(db.destroyFile("test.3"));
    CALL(db.destroyFile("test.4"));

    delete bufMgr;

    ///////////////////////////////////////////////////////////////////////////
    //                      OUR TEST CASES BELOW                             //
    ///////////////////////////////////////////////////////////////////////////

    cout << "--- Starting our tests ---" << endl;

    cout << "\n Starting Buffer Manager tests." << endl;
    /*********************************** buf.cpp test ****************/

    /* Recreate buffer manager */
    bufMgr = new BufMgr(num);

    /* Recreate and open test files */
    cout << "Creating and opening empty test files...\n";
    CALL(db.createFile("test.1"));
    ASSERT(db.createFile("test.1") == FILEEXISTS);
    CALL(db.createFile("test.2"));
    ASSERT(db.createFile("test.2") == FILEEXISTS);
    CALL(db.createFile("test.3"));
    ASSERT(db.createFile("test.3") == FILEEXISTS);
    CALL(db.createFile("test.4"));
    ASSERT(db.createFile("test.4") == FILEEXISTS);

    CALL(db.openFile("test.1", file1));
    CALL(db.openFile("test.2", file2));
    CALL(db.openFile("test.3", file3));
    CALL(db.openFile("test.4", file4));

    int pageNo;
    int firstPageNo;

    cout << "Allocating the first page in the file, which is special\n";
    CALL(bufMgr->allocPage(file1, firstPageNo, page));


    /* Clear stats and get ready for actual tests */
    cout << endl;
    bufMgr->clearBufStats();

    /* Test that disposePage on a pinned page returns PAGEPINNED */
    cout << "Testing that disposePage on a freshly allocated pinned page returns PAGEPINNED\n";
    CALL(bufMgr->allocPage(file1, pageNo, page)); //Allocate a new page
    ASSERT(bufMgr->disposePage(file1, pageNo) == PAGEPINNED); // Page is pinned, should fail
    CALL(bufMgr->unPinPage(file1, pageNo, 0, 0)); // Unpin the page

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    /* Test that disposePage on a non-pinned, legal to dispose page returns OK */
    cout << "Testing that disposePage on a non-pinned, legal to dispose page works\n";
    CALL(bufMgr->readPage(file1, pageNo, page)); // Read the page
    CALL(bufMgr->unPinPage(file1, pageNo, 0, 0)); // Page is unpinned
    CALL(bufMgr->disposePage(file1, pageNo)); // Should succeed

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    /* Test that disposePage on page 0 of a file returns BADPAGENO */
    cout << "Testing that disposePage on page 0 returns BADPAGENO\n";
    ASSERT(bufMgr->disposePage(file1, 0) == BADPAGENO);

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();

    /* Test that disposePage on the first user-created page returns BADPAGENO */
    cout << "Testing that disposePage on the first user created page of the file returns BADPAGENO\n";
    CALL(bufMgr->unPinPage(file1, firstPageNo, 0, 0));
    ASSERT(bufMgr->disposePage(file1, firstPageNo) == BADPAGENO);

    cout << "BUFSTATS:" << bufMgr->getBufStats() << endl;
    bufMgr->clearBufStats();



    CALL(db.closeFile(file1));
    CALL(db.closeFile(file2));
    CALL(db.closeFile(file3));
    CALL(db.closeFile(file4));

    CALL(db.destroyFile("test.1"));
    CALL(db.destroyFile("test.2"));
    CALL(db.destroyFile("test.3"));
    CALL(db.destroyFile("test.4"));



    /*********************** Buffer Manager Test **********************/

    File* file5;    
    (void)db.destroyFile("test.5");
    CALL(db.createFile("test.5"));
    ASSERT(db.createFile("test.5") == FILEEXISTS);
    CALL(db.openFile("test.5", file5));

    Page* testPage;
    Status errStatus;


    int pageNOs[100];

    db.createFile("test.5");

    delete bufMgr;
    bufMgr = new BufMgr(50);

    // allocate 50 pages
    for(int i = 0; i < 50; i++) {
        bufMgr->allocPage(file5,pageNOs[i],testPage);
    }


    // TEST 1: trying to allocate a 51st page
    cout << "BufMgr Test 1: ";
    errStatus = bufMgr->allocPage(file5,pageNOs[51],testPage);
    if(errStatus != BUFFEREXCEEDED) {
        cout << "ERROR-Buffer Manager did not handle full buffer error correctly!"
            << endl;
        exit(1);
    }
    else {
        cout << "SUCCESS-Buffer Manager handled full buffer error correctly." 
            << endl;
    }

    // unpin a page in the buffer pool
    bufMgr->unPinPage(file5,37,false,false);

    // TEST 2: try to allocate a 51st page again
    cout << "BufMgr Test 2: ";
    errStatus = bufMgr->allocPage(file5,pageNOs[51],testPage);
    if(errStatus != OK) {
        cout << "ERROR-Buffer Manager did not remove the unpinned page!" 
            << endl;
        exit(1);
    }
    else {
        cout << "SUCCESS-Buffer Manager made space in the buffer correctly." 
            << endl;
    }


    // TEST 3: try to unpin a file twice
    cout << "BufMgr Test 3: ";
    errStatus = bufMgr->unPinPage(file5,3,false,false);
    if(errStatus != OK) {
        cout << "ERROR-unPinPage() is broken." << endl;
        exit(1);
    }
    errStatus = bufMgr->unPinPage(file5,3,false,false);
    if(errStatus != PAGENOTPINNED) {
        cout << "ERROR-buffer manager did not keep correct pin count." 
            << endl;
        exit(1);
    }
    else {
        cout << "SUCCESS-Buffer manager kept correct pin count." << endl;
    }


    // TEST 4: try to unpin a page that isnt in the buffer pool
    cout << "BufMgr Test 4: ";
    errStatus = bufMgr->unPinPage(file5,999,false,false);
    if(errStatus != BUFMAPNOTFOUND) {
        cout << "ERROR-buffer manager unpinned a nonexistent frame."
            << endl;
        exit(1);
    }
    else {
        cout << "SUCCESS-Buffer manager returned the correct error for unpinning"
            << " a nonexistent frame." << endl;
    }

    for(int i = 0; i < 52; i++) {
        bufMgr->unPinPage(file5,pageNOs[i],false,false);
    }

    cout << "\n\n" << endl;
    /********************** Buffer Map Test ******************************/

    cout << "Starting Buffer Map tests.\n" << endl;
    BufMap *map = new BufMap();

    /* We're not actually valid file handles!  Don't use us! */
    File *f1 = (File*)(0xDEADBEEF);
    File *f2 = (File*)(0xF00DBEEF);
    File *f3 = (File*)(0xBADBEEF);
    File *f4 = (File*)(0xF9390FF);

    map->insert(f1,17,(unsigned int)170);
    map->insert(f2,17,(unsigned int)230);
    map->insert(f3,39,(unsigned int)390);

    errStatus = map->insert(f3,39,(unsigned int)390);
    if(errStatus != BUFMAPERROR) {
        cout << "Buffer Map returned the wrong error status!" << endl;
        exit(1);
    } 
    else {
        cout << "Buffer Map handled insert error correctly." << endl;
    }

    unsigned int f1FrameNo = -1;
    unsigned int f2FrameNo = -1;
    unsigned int f3FrameNo = -1;
    unsigned int f4FrameNo = -1;

    map->lookup(f1,17,f1FrameNo);
    map->lookup(f2,17,f2FrameNo);
    map->lookup(f3,39,f3FrameNo);

    cout << "File 1's frame number = " << f1FrameNo << "\texpected = 170" << endl;
    cout << "File 2's frame number = " << f2FrameNo << "\texpected = 230" << endl;
    cout << "File 3's frame number = " << f3FrameNo << "\texpected = 390" << endl;

    errStatus = map->lookup(f4,4,f4FrameNo);
    if(errStatus != BUFMAPNOTFOUND) {
        cout << "Buffer Map returned the wrong error status!" << endl;
        exit(1);
    }
    else {
        cout << "Buffer Map handled lookup error correctly." << endl;
    }

    errStatus = map->remove(f4,4);
    if(errStatus != BUFMAPNOTFOUND) {
        cout << "Buffer Map returned the wrong error status!" << endl;
        exit(1);
    }
    else {
        cout << "Buffer Map handled remove error correctly." << endl;
    }


    cout << "\n--- Our tests complete, cleaning up ---\n" << endl;

    ///////////////////////////////////////////////////////////////////////////
    //                      END OF OUR TEST CASES                            //
    ///////////////////////////////////////////////////////////////////////////

    cout << endl << "Passed all tests." << endl;

    return (1);
}
